/*

WinClip

   Copyright (C) 2011, Gowtham (gowthamgowtham@gmail.com)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

*/


#include "dialog.h"
#include "ui_dialog.h"
#include <QApplication>
#include <QMessageBox>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QCloseEvent>
#include <QDesktopServices>
#include <QUrl>

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    // Load icons
    enabledIcon = new QIcon(":/gowtham/winclip/happy.png");
    disabledIcon = new QIcon(":/gowtham/winclip/sad.png");
    tickIcon = new QIcon( ":/gowtham/winclip/tick.gif");
    emptyIcon = new QIcon();

    // Setup the User interface
    ui->setupUi(this);

    // Settings object
    settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, "Gowtham", "WinClip", this);
    settings->setDefaultFormat(QSettings::IniFormat);

    // Dialog to handle saved items
    savedItemsDialog = new SavedItemsDialog(this);

    // Cannot style a submenu alone
    // See http://stackoverflow.com/questions/5094498/how-to-style-a-submenu-item-in-qt
    // this->setStyleSheet("*[specialProp=true] { font: bold }");

    // Hide the ? button, show tge minimise button _
    Qt::WindowFlags flags = this->windowFlags();
    flags = flags & ~(Qt::WindowContextHelpButtonHint);
    flags = flags |   Qt::WindowMinimizeButtonHint;
    flags = flags & ~(Qt::WindowMaximizeButtonHint);
    this->setWindowFlags(flags);

    // Create tooltips
    QString spinText = QString("Min %1 Max %2").arg(ui->spinNumItems->minimum()).arg(ui->spinNumItems->maximum());
    ui->spinNumItems->setToolTip( spinText );

    // Menu
    menu = new QMenu();
    savedItemsMenu = new QMenu(this);
    savedItemsMenu->setTearOffEnabled(true);
    savedItemsMenu->setStyleSheet("QMenu{ font: bold }");
    savedItemsMenu->setTitle( "Saved Items" );
    savedItemsAction = menu->addMenu(savedItemsMenu);
    separator = menu->addSeparator();

    // Read settings and initialize saved items
    readSettings();

    // System tray
    sysTrayIcon = new QSystemTrayIcon(*getIcon(),this);
    sysTrayIcon->setContextMenu(menu);
    sysTrayIcon->show();
    sysTrayIcon->setToolTip("WinClip");
    menu->setTearOffEnabled(true);

    // Initialize clipboard
    clipboard = QApplication::clipboard();

    // Set the max number of items in UI
    ui->spinNumItems->setValue(maxItems);

    // Prevent dialog box resizing
    this->setFixedSize(this->width(),this->height());

    // Hook a callback when tray icon is activated
    connect(sysTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
                 this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));

    // Label link
    // qDebug() << connect(ui->lblLink, SIGNAL(linkActivated(QString)), this, SLOT(linkLabelClicked()));

    // Call this function to set the check box text. This is also a
    // slot which will be called by the QT runtime
    listeningToggledSlot();

    // Ask Qt to fire an event when user rightclicks the listbox
    ui->listHistory->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->listHistory, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(historyListMenuRequested(QPoint)));
    // Setup the menu
    historyListMenu = new QMenu(ui->listHistory);
    historyRemoveAction = historyListMenu->addAction("Remove selected");
    connect(historyRemoveAction, SIGNAL(triggered()), this, SLOT(historyRemoveSlot()));


    // Ask QT to open URL when user clicks on it
    // ui->lblLink->setOpenExternalLinks(true);

    showIntro();
}

void Dialog::showIntro()
{
    if( firstRun ) {
        QMessageBox introBox;
        introBox.setText("Welcome to WinClip 1.11");
        introBox.show();
        firstRun = false;
    }
}

QIcon *Dialog::getIcon()
{
    if( ui->chkListening->checkState() == Qt::Checked )
        return enabledIcon;
    return disabledIcon;
}

void Dialog::popupMenuClicked()
{
    resetTickIcon();

    // Find out who triggered us?
    QAction *action = qobject_cast<QAction*>(sender());
    action->setIcon(*tickIcon);
    long id = (long)action;
    if( history.find(id) != history.end() )
    {
        setClipboard(id);
    }
    else
        qDebug( "Something wrong!" );
}

bool Dialog::canClose()
{
    // Ask question only if we are monitoring clipboard. Otherwise,
    // just assume user wants to get rid of us
    if( ui->chkListening->checkState() != Qt::Checked )
        return true;

    QMessageBox mBox;
    mBox.setText( "Do you really want to quit? If you quit, you will not be able to retrieve "
                  "your clipboard history." );
    mBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
    //mBox.setDefaultButton(QMessageBox::No);
    mBox.setIcon(QMessageBox::Question);
    int answer = mBox.exec();
    if(answer == QMessageBox::Yes)
        return true;

    return false;
}

void Dialog::setClipboard(long id)
{
    clipboard->setText(history.value(id));
}

Dialog::~Dialog()
{
    if( sysTrayIcon )
        sysTrayIcon->hide();
    writeSettings();
    delete ui;
}

void Dialog::clipboardChangedSlot()
{
    qDebug( clipboard->text( QClipboard::Clipboard ).toAscii() );

    const QMimeData *mimeData = clipboard->mimeData(QClipboard::Clipboard);

    // Remove separator and saved items menu
    menu->removeAction(separator);
    menu->removeAction(savedItemsAction);

    // Reset the tick icon
    resetTickIcon();

    // Determine if this item is already in the saved item list
    long savedAction = alreadyInSavedItems();

    // If this is already in saved items, then do not bother anything
    if( mimeData->hasText() )
    {
        if( ! savedAction )
        {
            // First check if we already have this text. Otherwise, we will be adding the same
            // stuff multiple times in our history
            long aId;
            if((aId = alreadyInHistory()))
            {
                moveToTop(aId);
            }
            else
            {
                QString trimmed = trim(mimeData->text());
                QAction *id = menu->addAction(trimmed);
                resetTickIcon(); // Cheap way to reset the tick icon
                id->setIcon(*tickIcon);
                connect(id, SIGNAL(triggered()), this, SLOT(popupMenuClicked()));
                //menu->setActiveAction(id);
                history.insert((long)id, mimeData->text());
                forgetExtraItems();
            }
            updateHistoryListbox();
        }

        // If it is in saved items, then need to set the tick icon
        if( savedAction )
        {
            QAction *action = (QAction*)savedAction;
            action->setIcon(*tickIcon);
        }
    }

    // Restore separator and saved items menu
    menu->addAction(separator);
    menu->addAction(savedItemsAction);

    // Keep saving settings as and when we get new items. When Windows
    // is shut down, somehow, winclip is not exiting gracefully, so none
    // of the items are being saved... :(
    // This is a quick fix, also explore a timer?
    writeSettings();

}

void Dialog::resetTickIcon()
{
    QList<QAction*> actions = menu->actions();
    actions.append( savedItemsMenu->actions() );

    for( int i = 0; i < actions.size(); ++i )
        if( actions.at(i) )
            actions.at(i)->setIcon(*emptyIcon);
}

void Dialog::moveToTop(long id)
{
    resetTickIcon();
    QAction *qa = (QAction*)id;
    qa->setIcon(*tickIcon);
    menu->removeAction(qa);
    QList<QAction*> actions = menu->actions();
    actions.append(qa);
    menu->addActions(actions);
}


long Dialog::alreadyInSavedItems()
{
    QList<QAction*> actions = savedItemsMenu->actions();
    for( int i=0; i<actions.count(); ++i )
        if( history.find((long)actions.at(i)) != history.end() )
            if( history.value((long)actions.at(i)) == clipboard->text() )
                return (long)actions.at(i);

    return 0;
}

long Dialog::alreadyInHistory()
{
    // Improve this linear search
    QMapIterator<long,QString> it(history);
    while( it.hasNext() )
    {
        it.next();
        if( it.value() == clipboard->text() )
            return (long)it.key();
    }
    return 0;
}

void Dialog::forgetExtraItems()
{
    // The logic is quite simple. A menu action pointer is used as a
    // key in a hashmap to store the clipboard text. This is working!
    Q_ASSERT(!history.isEmpty());
    while(history.size() > maxItems)
    {
        QList<QAction*> keys = menu->actions();
        long id = (long)keys.at( 0  );
        history.remove(id);

        QAction *action = (QAction*)id;
        menu->removeAction(action);
    }
}

QString Dialog::trim(QString input)
{
    // Returned string is not more than 45 chars
    bool addDots = false;
    if( input.length() > 45 )
    {
        input.chop( input.length() - 45 );
        addDots = true;
    }
    // No leading, trailing whitespace
    input = input.trimmed();
    input = input.replace(QRegExp("\\s+")," ");

    if(addDots)
        input.append("..."); // Add ... to signify that text was truncated

    return input;
}

void Dialog::updateHistoryListbox()
{
    ui->listHistory->clear();
    QList<QAction*> actions = menu->actions();
    QAction *action;
    int index=-1;
    for(int i=0; i<actions.size(); ++i)
    {
        action = actions.at(i);
        if( action == savedItemsAction || action == separator )
            continue;

        ++index;
        long id = (long)action;
        ui->listHistory->addItem(trim(history.value(id)));
        ui->listHistory->item(index)->setToolTip(history.value(id));
    }
}

void Dialog::listeningToggledSlot()
{
    if( ui->chkListening->checkState() == Qt::Checked )
    {
        if( connectClipboard() )
        {
            ui->chkListening->setText( "Listening" );
        }
        else
        {
            QMessageBox mBox;
            mBox.setText("Error while connecting to clipboard");
            mBox.exec();
        }
    }
    else if( ui->chkListening->checkState() == Qt::Unchecked )
    {
        if( disconnectClipboard() )
        {
            ui->chkListening->setText( "Not listening" );
        }
        else
        {
            QMessageBox mBox;
            mBox.setText("Error while disconnecting clipboard");
            mBox.exec();
        }
    }
    sysTrayIcon->setIcon(*getIcon());
    setWindowIcon(*getIcon());
}

void Dialog::maxItemsChangedSlot()
{
    maxItems = ui->spinNumItems->value();
}

void Dialog::historyClearedSlot()
{

}

bool Dialog::connectClipboard()
{
    return connect(clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardChangedSlot()) );
}

bool Dialog::disconnectClipboard()
{
    return disconnect(clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardChangedSlot()) );
}

void Dialog::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason)
    {

    // Double click toggles dialog display
    case QSystemTrayIcon::DoubleClick:
        if( isVisible() )
            hide();
        else
        {
            show();
            raise();
            //setFocus();
        }
        break;

    // Middle click does nothing
    case QSystemTrayIcon::MiddleClick:
        break;

    // Single click shows popup menu, now now
    //case QSystemTrayIcon::Trigger:
    //    menu->exec();

    // Ignore anything else
    default:
        ;
    }
}

void Dialog::hideClicked()
{
    hide();
}

void Dialog::closeEvent(QCloseEvent *e)
{
    if(canClose())
        QCoreApplication::exit(0);

    e->ignore();
}

void Dialog::linkLabelClicked()
{
    qDebug() << "Link label clicked";
    if( ! QDesktopServices::openUrl( QUrl( ui->cmdLink->text() ) ) )
    {
        QMessageBox mbox;
        mbox.setIcon(QMessageBox::Critical);
        mbox.setText( "Failed to open url " + ui->cmdLink->text() );
        mbox.exec();
    }
}

void Dialog::createSavedItemsMenu(const QStringList &list)
{
    QList<QAction*> actions = savedItemsMenu->actions();
    for( int i=0; i<actions.count(); ++i )
    {
        long id = (long)actions.at(i);
        history.remove(id);
    }
    savedItemsMenu->clear();
    for( int i=0; i<list.count(); ++i )
    {
        QAction *action = savedItemsMenu->addAction(trim(list.at(i)));
        history.insert((long)action, list.at(i));
        connect(action, SIGNAL(triggered()), this, SLOT(popupMenuClicked()));
    }
}

void Dialog::savedItemsCmdClicked()
{
    QStringList *savedItems = getSavedItems();
    savedItemsDialog->prepare(savedItems);
    if( savedItemsDialog->exec() == QDialog::Accepted )
    {
        qDebug() << "Accepted";
        createSavedItemsMenu(savedItemsDialog->getSavedItems());
        writeSettings(); // Save our new items
    }
    delete savedItems;
}

QStringList* Dialog::getSavedItems()
{
    QList<QAction*> actions = savedItemsMenu->actions();
    QStringList *list = new QStringList();

    for( int i=0; i<actions.count(); ++i )
    {
        qDebug() << history.value((long)actions.at(i));
        if( history.find((long)actions.at(i)) != history.end() )
            list->append(history.value((long)actions.at(i)));
    }
    return list;
}

void Dialog::readSettings()
{
    // Restore settings
    settings->beginGroup("configuration");
    maxItems = settings->value( "maxitems", 20 ).toInt();
    firstRun = settings->value( "firstrun", true ).toBool();
    settings->endGroup();

    // Restore items!
    QStringList savedItems;
    settings->beginGroup("items");
    int size = settings->beginReadArray("items");
    for( int i=0; i<size; ++i )
    {
        settings->setArrayIndex(i);
        savedItems.append(settings->value("item").toString() );
    }
    settings->endArray();
    settings->endGroup();
    addRestoredItems(savedItems);

    // Restore saved items
    settings->beginGroup("saveditems");
    size = settings->beginReadArray( "items" );
    savedItems.clear();
    for( int i=0; i<size; ++i )
    {
        settings->setArrayIndex(i);
        savedItems.append( settings->value("item").toString() );
    }
    settings->endArray();
    settings->endGroup();
    createSavedItemsMenu(savedItems);

}

void Dialog::addRestoredItems(const QStringList &list)
{
    menu->removeAction(separator);
    menu->removeAction(savedItemsAction);

    for( int i=0; i<list.size(); ++i )
    {
        //qDebug() << "Adding " << list.at(i);
        QString trimmed = trim(list.at(i));
        QAction *id = menu->addAction(trimmed);
        connect(id, SIGNAL(triggered()), this, SLOT(popupMenuClicked()));
        history.insert((long)id, list.at(i));
        //qDebug() << "Done " << list.at(i);
    }

    menu->addAction(separator);
    menu->addAction(savedItemsAction);

    updateHistoryListbox();
}

void Dialog::writeSettings()
{
    settings->beginGroup("saveditems");
    settings->beginWriteArray("items");
    QList<QAction*> actions = savedItemsMenu->actions();
    QAction *action = NULL;
    long id = 0;
    int index = 0;
    for( int i=0; i<actions.count(); ++i )
    {
        action = actions.at(i);
        id = (long)action;
        // Something is in menu but not in our history?
        if( history.find(id) == history.end() )
        {
            qDebug() << "While saving saved items, don't know what " << action->text() << " is!";
            continue;
        }
        settings->setArrayIndex(index++);
        settings->setValue("item", history.value(id));
    }
    settings->endArray();
    settings->endGroup();

    settings->beginGroup("configuration");
    settings->setValue("maxitems", maxItems);
    settings->setValue("firstrun", firstRun);
    settings->endGroup();

    settings->beginGroup("items");
    settings->beginWriteArray("items");
    index = 0;
    for( int i=0; i<menu->actions().count(); ++i )
    {
        action = menu->actions().at(i);
        if( action == savedItemsAction || action == separator )
            continue;
        long id = (long)action;
        if( history.find(id) == history.end() )
        {
            qDebug() << "While saving items, don't know what " << action->text() << " is!";
            continue;
        }
        settings->setArrayIndex(index++);
        settings->setValue("item",history.value(id));
    }
    settings->endArray();
    settings->endGroup();

    qDebug() << settings->fileName();
}

void Dialog::historyListMenuRequested(QPoint point)
{
    historyListMenu->exec(QCursor::pos());
}

void Dialog::historyRemoveSlot()
{
    int row = ui->listHistory->currentRow();
    if( row < 0 )
        return;

    // Remove separator and saved items menu
    menu->removeAction(separator);
    menu->removeAction(savedItemsAction);

    // In the listbox and the menu, order is same. Traverse the
    // menu actions to find the item to remove
    QAction *action = menu->actions().at(row);
    menu->removeAction(action); // Remove from popup
    delete ui->listHistory->takeItem(row); // Remove from listbox
    long id = (long)action; // Remove from hash table
    history.remove(id);

    // Restore separator and saved items menu
    menu->addAction(separator);
    menu->addAction(savedItemsAction);
}
